﻿
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using My.Ioc;
using My.Ioc.Configuration.FluentApi;
using My.Ioc.Configuration.Injection;
using My.Ioc.Core;
using My.Ioc.Dependencies;
using My.Ioc.Injection;

namespace ImplementDecorator
{
    #region Extensions

    public class ReflectionDecoratorConstructorInjector<T> : Injector<T>
    {
        readonly ConstructorInfo _constructorInfo;

        public ReflectionDecoratorConstructorInjector(ConstructorInfo constructorInfo)
        {
            _constructorInfo = constructorInfo;
        }

        public override void Execute(InjectionContext<T> context)
        {
            var instance = _constructorInfo.Invoke(new object[] {context.Instance});
            InjectInstanceIntoContext(context, (T)instance);
        }
    }

    public class ReflectionDecoratorInjectionConfigurationInterpreter : IInjectionConfigurationInterpreter
    {
        readonly TypedInjectionConfigurationGroup _configGroup;

        public ReflectionDecoratorInjectionConfigurationInterpreter(TypedInjectionConfigurationGroup configGroup)
        {
            _configGroup = configGroup;
        }

        public InjectionConfigurationGroup InjectionConfigurationGroup
        {
            get { return _configGroup; }
        }

        public Injector<T> Parse<T>(Kernel kernel, ObjectDescription description, out List<DependencyProvider> groupDepProviders)
        {
            var ctorConfigItem = _configGroup.ConstructorInjectionConfigurationItem;
            groupDepProviders = null;
            return new ReflectionDecoratorConstructorInjector<T>(ctorConfigItem.Constructor);
        }
    }

    public static class DecoratorApi
    {
        public static ICommonConfigurationApi ApplyDecorator(this ICommonConfigurationApi typeConfig, Type decoratedType)
        {
            if (typeConfig == null)
                throw new ArgumentException();
            if (decoratedType == null)
                throw new ArgumentException();

            var configSet = typeConfig.InjectionConfigurationSet;

            var description = configSet.ObjectDescription;
            if (!description.ContractType.IsAssignableFrom(decoratedType))
                throw new ArgumentException();

            var ctor = decoratedType.GetConstructor(new Type[] { typeof(Service) });
            //var parameters = new PositionalParameterSet(Parameter.Positional<Service>());
            //var option = typeConfig.Kernel.ContainerOption;
            //var ctor = option.ConstructorSelector.SelectConstructor(decoratedType, parameters, option.MaxConstructorParameterLength);

            IConstructorInjectionConfigurationItem ctorConfigItem = new ConstructorInjectionConfigurationItem(ctor, null);
            var configGroup = new TypedInjectionConfigurationGroup(description, ctorConfigItem);
            configSet.AddCustomInjectionConfigurationGroup(configGroup);
            configGroup.InjectionConfigurationInterpreter = new ReflectionDecoratorInjectionConfigurationInterpreter(configGroup);
            return typeConfig;
        }
    }

    #endregion

    #region Test Types

    public class Service
    {
        public virtual void Execute()
        {
            Console.WriteLine("Service execute...");
        }
    }

    public class DecoratedService : Service
    {
        readonly Service _baseService;

        public DecoratedService(Service baseService)
        {
            _baseService = baseService;
        }

        public override void Execute()
        {
            Log();
            _baseService.Execute();
        }

        static void Log()
        {
            Console.WriteLine("DecoratedService execute...");
        }
    }

    #endregion

    class Program
    {
        static void Main(string[] args)
        {
            var container = new ObjectContainer(false);
            container.Register<Service>()
                .ApplyDecorator(typeof(DecoratedService))
                .In(Lifetime.Container());

            container.CommitRegistrations();

            var service = container.Resolve<Service>();
            Debug.Assert(service != null);
            Debug.Assert(service is DecoratedService);

            var service2 = container.Resolve<Service>();
            Debug.Assert(service == service2);

            Console.WriteLine("The ImplementDecorator success...");
            Console.ReadLine();
        }
    }
}
